"""
This module contains the LIME container class and is responsible
for displaying the components and handling user interaction.
"""
"""
   Copyright 2011 Gabriel Elkind and Brian Gapinski

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
"""

import os
import sys
import ConfigParser
import logging

#http://www.wxpython.org/download.php#stable
import wx
import wx.lib.agw.aui as aui

import config
import console
import model
import editview
import process
import filebrowser

# These ids are used as bindings for events and handlers
# that are not built in to wx.
ID_RUN = 1 
ID_PREF = 2 
ID_SAVE_ALL = 3 
ID_QUIT = 4 
ID_COPY = 5 
ID_CUT = 6 
ID_PASTE = 7 

#Creates the log files
logging.basicConfig(filename=os.path.join(os.path.expanduser('~'), 'lime.log'), 
                    level=logging.DEBUG)

#Build the logger object and set to debug mode 
logger = logging.getLogger('lime')
logger.setLevel(logging.DEBUG)

class Lime(wx.Frame):
    """
    This is the container and parent for all the GUI components. It
    helps to mediate inter-component communication. The core modules
    by default are:

        - Model: The files that compose the LII project.
        - Editor: The tool used to edit LII project files.
        - Filebrowser: The tool used to view the LII project file hierarchy.
        - Console: The tool used for viewing the LII output.
 

    @type _config: Config
    @ivar _config: The user config settings for lime

    @type _exe: string
    @ivar _exe: The path to the Landis-II executable

    @type _autosave_on_run: bool
    @ivar _autosave_on_run: True to enable autosaving of files when 'run'
                            is selected, False otherwise

    @type _menubar: wx.Menubar
    @ivar _menubar: The menubar for lime

    @type _toolbar: wx.Toolbar
    @ivar _toolbar: The toolbar for lime

    @type _model: Model
    @ivar _model: The current model being viewed/edited

    @type _process: Process
    @ivar _process: The LII process to be run

    @type _console: Console
    @ivar _console: Displays the output of the process

    @type _ev: EditView
    @ivar _ev: The component used for editing and viewing files

    @type _fb: Filebrowser
    @ivar _fb: Displays a tree representation of the model

    @type _aui: Advanced User Interface
    @ivar _aui: Packs and manages the graphical components
    """
    def __init__(self, title):
        """
        This module is the container and mediator for all of the
        GUI components and the Landis-II model.

        @param title: The name of the frame
        @type title: string
        """
        # Constructor of the super class
        wx.Frame.__init__(self, None, wx.ID_ANY, title, size=(850,750))
        
        # The LIME menubar
        self._menubar = wx.MenuBar()

        # The model that represents the LII project
        self._model = model.Model()

        # The process to be run by LIME
        self._process = process.Process('', -1)

        # Container for all the components
        self._aui = aui.AuiManager()
        self._aui.SetManagedWindow(self)

        self._toolbar = aui.AuiToolBar(self)

        # The config settings
        self._config = config.Config(self)
        self._exe = self._config.GetPath()
        self._autosave_on_run = self._config.GetAutosave()

        # The console that redirects the output
        self._console = console.Console(self)

        # The tool used to modify LII files
        self._ev = editview.EditView(self)
        self._model.Register(self._ev)

        # The tool used to view the file hierarchy
        self._fb = filebrowser.FileBrowser(self)
        self._model.Register(self._fb)

        # Creates LIME
        self._InitMenu()
        self._InitGUI()
        
        self.Bind(wx.EVT_CLOSE, self._QuitProject, self)

        # Center and display the frame
        self.Centre()
        self.Show(True)
        
    def _InitMenu(self):
        """
        This function is used to create the default menu for LIME
        """
        # Creates menu bars and parent menus
        self._menubar = wx.MenuBar()
        file_menu = wx.Menu()
        edit = wx.Menu()
        view = wx.Menu()
        run_menu = wx.Menu()
        pref = wx.Menu()
        help_menu = wx.Menu()

        # Adds menus to menubar
        self._menubar.Append(file_menu, '&File')
        self._menubar.Append(edit, '&Edit')
        self._menubar.Append(view, '&View')
        self._menubar.Append(run_menu, '&Run')
        self._menubar.Append(pref, '&Preferences')
        self._menubar.Append(help_menu, '&Help')

        # Create menu and toolbar items similtaneously 
        # with the _CreateItem method
        self._CreateItem('New', 
                         '\tCtrl+N', 
                         file_menu,
                         wx.ID_NEW, 
                         self._NewProject,
                         'icons/new.png')
        self._CreateItem('Open',
                         '\tCtrl+O',
                         file_menu,
                         wx.ID_OPEN,
                         self._OpenProject,
                         'icons/open.png')
        self._CreateItem('Save',
                         '\tCtrl+S',
                         file_menu,
                         wx.ID_SAVE,
                         self._SaveCurrent,
                         '')
        self._CreateItem('Save All',
                         '',
                         file_menu,
                         ID_SAVE_ALL,
                         self._SaveProject,
                         'icons/save.png')
        self._CreateItem('Add File',
                         '',
                         file_menu,
                         wx.ID_ADD,
                         self._AddFile,
                         '')

        self._toolbar.AddSeparator()

        self._CreateItem('Copy',
                         '\tCtrl+c',
                         edit, 
                         ID_COPY,
                         self._Copy, 
                         'icons/copy.png')

        self._CreateItem('Cut',
                         '\tCtrl+x',
                         edit,
                         ID_CUT,
                         self._Cut, 
                         'icons/cut.png')

        self._CreateItem('Paste',
                         '\tCtrl+p',
                         edit,
                         ID_PASTE,
                         self._Paste,
                         'icons/paste.png')

        
        file_menu.AppendSeparator()
        self._toolbar.AddSeparator()

        self._CreateItem('Run',
                         '\tCtrl+R',
                         run_menu,
                         ID_RUN,
                         self._RunProject,
                         'icons/run.png')
        self._CreateItem('Preferences',
                         '',
                         pref,
                         ID_PREF,
                         self._ChangePref,
                         'icons/config.png')

        self._CreateItem('Quit',
                         '\tCtrl+Q',
                         file_menu,
                         ID_QUIT,
                         self._QuitProject,
                         '')


        # Displays the menubar and toolbar
        self.SetMenuBar(self._menubar)
        self._toolbar.Realize()

    def _CreateItem(self, name, shortcut, parent, bind, handler, img):
        """
        Creates the menu and toolbar items

        @param name: The name of the item
        @type name: string
        
        @param shortcut: The shortcut (if any) of the item
        @type shortcut: string
        
        @param parent: The parent menu of the item
        @type parent: wx.Menu
        
        @param bind: The id associated to the item
        @type bind: int
        
        @param handler: The name of the method to call when an event occurs
        @type handler: EvtHandler
        
        @param img: The name of the png image to use for the toolbar
        @type img: string
        """
        # Create the menu item
        item = wx.MenuItem(parent, bind, name+shortcut)

        # and place inside parent menu
        parent.AppendItem(item)

        # Bind handler
        self.Bind(wx.EVT_MENU, handler, id=bind)

        # If an image representation of this method, add to the toolbar
        if img != '':
            image = wx.Image(img, wx.BITMAP_TYPE_PNG).ConvertToBitmap()
            self._toolbar.AddSimpleTool(bind, name, image, '')
            self.Bind(wx.EVT_TOOL, handler, id=bind)

    def _InitGUI(self):
        """
        Initializes all the GUI components and packs them into
        a container. This includes the editor, filebrowser,
        and console.
        """
        self._aui.AddPane(self._fb, 
                          aui.AuiPaneInfo().Left().Caption('FileBrowser'))
        self._aui.AddPane(self._ev, 
                          aui.AuiPaneInfo().CenterPane().Caption('Editor'))
        self._aui.AddPane(self._console, 
                          aui.AuiPaneInfo().Bottom().Caption('Console'))
        self._aui.AddPane(self._toolbar, 
                          aui.AuiPaneInfo().Top().ToolbarPane().Resizable(True))

        self._aui.Update()


    def _NewProject(self, event):
        """ 
        Opens a new project when choice is selected from the menu
        or toolbar.
        """
        # Gets the directory to the lime src
        limedirectory = os.path.dirname(os.path.abspath(__file__))

        #Ask the user where they would like to build the new file 
        #and what they'd like to call it
        text = 'Where would you like to build the new project'
        style1 = wx.DD_CHANGE_DIR
        new_dir_dialog = wx.DirDialog(self, message=text, style=style1)
        if not new_dir_dialog.ShowModal() == wx.ID_CANCEL:
            new_dir = new_dir_dialog.GetPath()
            logger.debug('Working directory is: %s', new_dir_dialog.GetPath())
     
            #Write the generic files to disk
            newprojecttemplatedir = os.path.join(limedirectory,'newtemp')
            listing = os.listdir(newprojecttemplatedir)
            
            for filename in listing:
                    
                #Get the content of each file in the template folder
                abspath = os.path.join(newprojecttemplatedir,filename)
                ofile = open(abspath,'r')
                filecontents = ofile.read()
                ofile.close()
         
                #and write each to a new file
                newfile = os.path.join(new_dir,filename)
                onewfile = open(newfile,'w')
                onewfile.write(filecontents)
             
            #Open the new project
            self._model.SetProject('scenario.txt')

    def _SetModelProject(self,scenario_file):
        """
        Safely sets the model's project to the files
        specfied in the scenario file

        @param scenario_file: The path to the file to base the project off of
        @type scenario_file: string
        """
        try:
            self._model.SetProject(scenario_file)
        except IOError as (erno,erstr):
            unfound_file = erstr
            dlg = wx.MessageDialog(self,
                                   "The following file was not detected by "
                                   "LIME: " + unfound_file + 
                                   "\n\nThis file will now be created",
                                   "",
                                   wx.OK)
            dlg.ShowModal()
            dlg.Destroy()
            open(unfound_file,'w').close()
            self._SetModelProject(scenario_file)

    def _OpenProject(self, event):
        """
        Opens a user selected LII project to be edited. 
        If a project is already open, ask the user if
        they would like to save
        """
        # Iterate through every file, deciding whether to save
        # or discard changes
        #AllFiles = self._model.GetAllFiles()
        #for filename in AllFiles:
        #    if not self._CheckSave(filename):
        #        event.Skip()
        #        break
        self._CheckSave()

        # Prompt user for a scenario file
        scenario_file = self._GetScenario()

        # Pass this information to the model
        self._SetModelProject(scenario_file)

        # Gets the information for the process
        ### The model call needs to be fixed
        cmd = self._exe + ' ' + self._model.GetTree().GetName()
        self._process.SetCmd(cmd)

    def _GetScenario(self):
        """
        Prompts the user to select a scenario file in a filedialog
        
        @return: The file chosen 
        @rtype: string
        """
        text = 'Select the scenario file'
        style1 = wx.FD_OPEN|wx.FD_CHANGE_DIR
        scenario_file = wx.FileDialog(self, message=text, style=style1)
        scenario_file.ShowModal()
        return scenario_file.GetFilename() 

    def _SaveCurrent(self, event):
        """
        Saves only the tab that is currently being viewed.
        """
        self._model.Save(self._ev.GetCurrText(), self._ev.GetCurrFile())
        #self._model.SetSync(True, self._ev.GetCurrFile())

    def _SaveFile(self, filename):
        """
        Saves the file filename

        @param filename: The file to be saved
        @type filename: string
        """
        self._model.Save(self._ev.GetText(filename), filename)


    def _SaveProject(self, event):
        """
        Saves all tabs that are currently open in the editor.
        """
        #for i in range(self._ev.GetTabCount()):
        #    self._model.Save(self._ev.GetText(i), self._ev.GetFile(i))
        #    self._model.SetSync(True, self._ev.GetFile(i))
        files = self._ev.GetAll()
        for filename in files:
            self._model.Save(files[filename], filename)

    def _CheckSave(self):
        """
        Confirms the user's decision to terminate the program without saving
        the specified file

        @rtype: bool
        @return: True if the user saved the file, False otherwise
        """
        for filename in self._model.GetAllFiles():
        
            #Unless the buffer is out of sync with the file, don't do anything
            if self._model.GetSync(filename) == False: 

                #Ask user if they want to save the changes
                message = "WARNING: Unsaved changes made to "+filename
                message += "\nWould you like to save this file?"
                savecheck = wx.MessageDialog(self,message,"Warning",\
                    wx.YES|wx.NO|wx.CANCEL)
                result = savecheck.ShowModal()

                #Save or discard, depending on their response
                if result == wx.ID_YES:
                    self._SaveFile(filename)

                #If the user cancels this throw runtime to abort this process
                if result == wx.ID_CANCEL:
                    #raise Runtime Error
                    return False

                #Scrap the modal
                savecheck.Destroy()
        
        return True

    def _AddFile(self, event):
        """
        Adds a new file to the current LII project.
        """
        wcd = 'All files (*)|*|Editor files (*.ef)|*.ef|'
        text = 'Please select file'
        file = wx.FileDialog(self,
                             message=text,
                             defaultDir=os.getcwd(),
                             defaultFile='',
                             wildcard=wcd,
                             style=wx.FD_OPEN)
        file.ShowModal()
        self._ev.AddFile(file.GetFilename())

    def _QuitProject(self, event):
        """
        Quits the LII application. <Insert actions taken on quit>
        """
        #Try going through every buffer and checking whether user
        #wants to save or discard each.
        #try:
        #for filename in self._model.GetAllFiles(): 
        #    if not self._CheckSave(filename):
        #        event.Skip()
        #        break
        if self._CheckSave():

            self.Destroy()
            wx.Exit()
        # If the user pressed cancel, the process will be aborted
        # by raising a RuntimeError
        #except RuntimeError:
        #    pass


    def _Copy(self,event):
        """
        Copies the selected text to the clipboard
        """
        self._ev.Copy()	 

    def _Cut(self,event):
        """
        Cuts the selected text
        """
        self._ev.Cut()

    def _Paste(self,event):
        """
        Pastes the contents of the clipboard to the buffer
        """
        self._ev.Paste()

    def _RunProject(self, event):
        """
        Checks if the files have been saved befor executing the process.
        """
        if self._autosave_on_run:
            self._SaveProject(event=None)
            self._BeginProcess()

        elif self._CheckSave():
            #for filename in self._model.GetAllFiles():
            #    if not self._CheckSave(filename):
            #        event.StopPropagation()
            #        event.Skip()
            #        break
            self._BeginProcess()

    def _BeginProcess(self):
        """
        Starts the L-II process
        """
        ### This should probably be done with a call to a model method
        root = open(self._model.GetTree().GetName(), 'r')
        for line in root:
            if 'Duration' in line:
                for word in line.split():
                    if word.isdigit():
                        end_time = int(word)
                        break               
            
        step = 1000/(end_time/5)
        self._process.SetStep(step)

        self._process.Start()

    def _ChangePref(self, event):
        """
        Handles the event when a user selects the change preferences
        option. As of now it only has ChangeExePath, but more
        options will probably be added.
        """
        self._config.ChangePref()


    def ChangePref(self):
        """
        Notifies LIME that the user has changed their preferences
        """
        self._exe = self._config.GetPath()
        cmd = self._exe + ' ' + self._model.GetTree().GetName()
        self._process.SetCmd(cmd)

        self._autosave_on_run = self._config.GetAutosave()

    def GetManager(self):
        """
        Returns the object in charge of managing the GUI components

        @return: The object that is meant to hold the components
        @rtype: wx.lib.agw.aui.AdvancedManager
        """
        return self._aui
        
    def SelectFile(self, filename):
        """
        Switches the current file being edited to filename.

        @param filename: The new file to be edited
        @type filename: string
        """
        self._ev.AddFile(filename)

    def OpenError(self, filename, line, error):
        """
        Notifies the editor to bring up the appropriate file and display
        the error.

        @param filename: The name of the file that has an error
        @type filename: string

        @param line: The line that contains the error
        @type line: int

        @param error: The string that is causing an error
        @type error: string
        """
        logger.debug('Received error notification')
        self._ev.OpenError(filename, line, error)

    def GetProcess(self):
        """
        Returns the process of this lime session

        @return: The process object of lime
        @rtype: wx.Process
        """
        return self._process


if __name__ == "__main__":
    app = wx.App()
    Lime('LIME')
    app.MainLoop()
